package com.axonactive.yolo.monitor;

import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.mail.MessagingException;

import org.apache.log4j.Logger;
import org.atmosphere.cpr.Broadcaster;
import org.atmosphere.cpr.BroadcasterFactory;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.context.annotation.PropertySource;

import com.axonactive.yolo.domain.FlightNotification;
import com.axonactive.yolo.domain.SubscribedFlight;
import com.axonactive.yolo.domain.User;
import com.axonactive.yolo.domain.UserEmail;
import com.axonactive.yolo.dto.Email;
import com.axonactive.yolo.dto.FlightNotificationDTO;
import com.axonactive.yolo.dto.Parameters;
import com.axonactive.yolo.exception.FlightNotificationNotFoundException;
import com.axonactive.yolo.exception.SubcribedFlightNotFoundException;
import com.axonactive.yolo.notification.FlightNotifier;
import com.axonactive.yolo.service.interfaces.EmailService;
import com.axonactive.yolo.service.interfaces.FlightNotificationService;
import com.axonactive.yolo.service.interfaces.ParamLockerService;
import com.axonactive.yolo.service.interfaces.SubscribedFlightService;
import com.axonactive.yolo.service.interfaces.SubscribedFlightUserIdService;
import com.axonactive.yolo.service.interfaces.UserEmailService;
import com.axonactive.yolo.service.interfaces.UserService;
import com.axonactive.yolo.util.CONSTANTS;
import com.axonactive.yolo.util.Utils;

@PropertySource("classpath:messages.properties")
public class FlightSubscriber implements Observer {

	private EmailService emailService;

	private UserService userService;

	private SubscribedFlightService subcribedFlightService;

	private SubscribedFlightUserIdService subscribedFlightUserIdService;

	private UserEmailService userEmailService;

	private FlightNotificationService flightNotificationService;

	private ParamLockerService paramLockerService;

	private Observerable observerable;

	private Map<Parameters, List<SubscribedFlight>> maps = new Hashtable<Parameters, List<SubscribedFlight>>();
	private final static Logger LOGGER = Logger
			.getLogger(FlightSubscriber.class);

	public FlightSubscriber(Observerable observerable) {
		this.observerable = observerable;
		this.observerable.register(this);
		initService();
	}

	private void initService() {
		this.emailService=(EmailService) ServiceProvider.getInstance().getService(EmailService.class);
		this.userService= (UserService) ServiceProvider.getInstance().getService(UserService.class);
		this.subcribedFlightService= (SubscribedFlightService) ServiceProvider.getInstance().getService(SubscribedFlightService.class);
		this.subscribedFlightUserIdService= (SubscribedFlightUserIdService) ServiceProvider.getInstance().getService(SubscribedFlightUserIdService.class);
		this.userEmailService= (UserEmailService) ServiceProvider.getInstance().getService(UserEmailService.class);
		this.flightNotificationService= (FlightNotificationService) ServiceProvider.getInstance().getService(FlightNotificationService.class);
		this.paramLockerService= (ParamLockerService) ServiceProvider.getInstance().getService(ParamLockerService.class);
	}

	/**
	 * Receive the notification from FlightObserverable This method is
	 * responsible for: + update the new information of flight to db. + send
	 * email to user + send a alert to notification center. + send SMS alert
	 *
	 * @param flights
	 *            subscribed flights which have new information
	 * @param cancelledFlights
	 *            list of flights were cancelled
	 * @param cancelledFlights2
	 * @throws SubcribedFlightNotFoundException
	 */
	public void update(List<SubscribedFlight> changePriceFlights,
			List<SubscribedFlight> changeScheduleFlights,
			List<SubscribedFlight> cancelledFlights)
			throws SubcribedFlightNotFoundException {

		int flightId;
		// create list of change price and schedule
		List<SubscribedFlight> flights = getChangePriceAndScheduleFlights(
				changePriceFlights, changeScheduleFlights);
		List<User> users = new ArrayList<>();
		if (cancelledFlights.size() > 0) {
			Iterator<SubscribedFlight> it = cancelledFlights.iterator();
			while (it.hasNext()) {
				SubscribedFlight subscribedFlight = it.next();
				// get users who subscribed this flight
				users = subscribedFlightUserIdService.findUsersByFlightId(subscribedFlight.getFlightId());

				// notify to client for cancel
				notifyPriceToClientForCancel(subscribedFlight, users);

				// send email
//				sendEmailToListUsersForCancel(subscribedFlight, users);

				// send SMS
				FlightNotifier.getInstance().sendSMSForCancelFlight(subscribedFlight, users);

				subscribedFlight.setAvailable(CONSTANTS.FLIGHT_UNAVAILABLE);
				subcribedFlightService.updateSubscribedFlight(subscribedFlight);
			}
		}
		// get flight id
		Logger.getLogger(FlightSubscriber.class).info("IN Fligth Subscriber");

		Iterator<SubscribedFlight> it = flights.iterator();
		while (it.hasNext()) {
			SubscribedFlight subscribedFlightNew = it.next();
			flightId = subscribedFlightNew.getId();

			SubscribedFlight subscribedFlight = subcribedFlightService
					.findById(flightId);

			// save old data
			subscribedFlight.setPriceBefore(subscribedFlight.getPrice());
			subscribedFlight.setOutBoundDepartureTimeBefore(subscribedFlight
					.getOutBoundDepartureTime());
			subscribedFlight.setOutBoundArrivalTimeBefore(subscribedFlight
					.getOutBoundArrivalTime());
			subscribedFlight.setInBoundDepartureTimeBefore(subscribedFlight
					.getInBoundDepartureTime());
			subscribedFlight.setInBoundArrivalTimeBefore(subscribedFlight
					.getInBoundArrivalTime());

			// update new change
			subscribedFlight.setPrice(subscribedFlightNew.getPrice());
			subscribedFlight.setOutBoundDepartureTime(subscribedFlightNew
					.getOutBoundDepartureTime());
			subscribedFlight.setOutBoundArrivalTime(subscribedFlightNew
					.getOutBoundArrivalTime());
			subscribedFlight.setInBoundDepartureTime(subscribedFlightNew
					.getInBoundDepartureTime());
			subscribedFlight.setInBoundArrivalTime(subscribedFlightNew
					.getInBoundArrivalTime());

			// save to database
			subcribedFlightService.updateSubscribedFlight(subscribedFlight);

			// get users who subscribed this flight
			users = subscribedFlightUserIdService.findUsersByFlightId(subscribedFlight.getFlightId());

			// notify to client for change
			notifyPriceToClientForChange(subscribedFlightNew, subscribedFlight, users, changePriceFlights, changeScheduleFlights);
			// send email
			sendEmailToListUserForChanging(subscribedFlight, users);

			// send SMS
//			if (changeScheduleFlights.contains(subscribedFlightNew)){
//				FlightNotifier.getInstance().sendSMSForChangeScheduleFlight(subscribedFlight, users);
//			}
//			if (changePriceFlights.contains(subscribedFlightNew)){
//				FlightNotifier.getInstance().sendSMSForChangePriceFlight(subscribedFlight, users);
//			}
		}
	}

	private void notifyPriceToClientForCancel(SubscribedFlight subcribedFlight, List<User> users) {
		// notify to user and save to notification db
		Broadcaster broadcaster;

		for (User user: users) {
			if(!user.isSendViaEmail()){
				continue;
			}

			FlightNotification flightNotification = new FlightNotification();
			flightNotification.setUserId(user.getId());
			flightNotification.setFlightId(subcribedFlight.getFlightId());
			flightNotification.setFlightRoute(subcribedFlight.getOutboundCode()
					+ "-" + subcribedFlight.getInboundCode());
			flightNotification.setFlightDepartureTime(subcribedFlight
					.getOutBoundDate()
					+ " "
					+ subcribedFlight.getOutBoundDepartureTime());
			flightNotification.setOldPrice(subcribedFlight.getPriceBefore());
			flightNotification.setNewPrice(subcribedFlight.getPrice());
			flightNotification.setCurrencyCode(subcribedFlight
					.getCurrencyCode());
			flightNotification.setFlightChangeType(2);
			DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			Date date = new Date();
			flightNotification.setSaveTime(dateFormat.format(date));

			ObjectMapper objectMapper = new ObjectMapper();
			String json;

			if ((broadcaster = BroadcasterFactory.getDefault().lookup(user.getId())) != null) {
				// user with id i is online
				flightNotification.setStatus(0);
				FlightNotification temp = flightNotificationService
						.create(flightNotification);
				List<FlightNotification> listFlightJson = new ArrayList<FlightNotification>();
				listFlightJson.add(temp);
				List<FlightNotificationDTO> flightNotificationDTOs = flightNotificationService
						.convertToListFlightNotificationDTO(listFlightJson);

				try {

					json = objectMapper.writeValueAsString(flightNotificationDTOs);
					json = "10" + json;

					if (!BroadcasterFactory.getDefault().lookup(user.getId())
							.broadcast(json).isDone()) {
						temp.setStatus(0);
						try {
							flightNotificationService.update(temp);
						} catch (FlightNotificationNotFoundException e) {
							LOGGER.error("database error", e);
						}
					}
				} catch (JsonGenerationException e) {
					LOGGER.error("json error", e);
				} catch (JsonMappingException e) {
					LOGGER.error("json error", e);
				} catch (IOException e) {
					LOGGER.error("io error", e);
				}

			} else {
				flightNotification.setStatus(0);
				flightNotificationService.create(flightNotification);
			}

		}
	}

	private void notifyPriceToClientForChange(
			SubscribedFlight subcribedFlightNew,
			SubscribedFlight subcribedFlight, List<User> users,
			List<SubscribedFlight> changePriceFlights,
			List<SubscribedFlight> changeScheduleFlights) {
		// notify to user and save to notification db
		for(User user: users){
			if(!user.isSendViaEmail()){
				continue;
			}

			FlightNotification flightNotification = new FlightNotification();
			flightNotification.setUserId(user.getId());
			flightNotification.setFlightId(subcribedFlight.getFlightId());
			flightNotification.setFlightRoute(subcribedFlight.getOutboundCode()
					+ "-" + subcribedFlight.getInboundCode());
			flightNotification.setFlightDepartureTime(subcribedFlight
					.getOutBoundDate()
					+ " "
					+ subcribedFlight.getOutBoundDepartureTime());
			if (changePriceFlights.contains(subcribedFlightNew)
					&& !changeScheduleFlights.contains(subcribedFlightNew)) {
				flightNotification
						.setOldPrice(subcribedFlight.getPriceBefore());
				flightNotification.setNewPrice(subcribedFlight.getPrice());
				flightNotification.setFlightChangeType(0);
			} else if (changeScheduleFlights.contains(subcribedFlightNew)
					&& !changePriceFlights.contains(subcribedFlightNew)) {
				flightNotification.setOldPrice(0);
				flightNotification.setNewPrice(0);
				flightNotification.setFlightChangeType(1);
			} else if (changeScheduleFlights.contains(subcribedFlightNew)
					&& changePriceFlights.contains(subcribedFlightNew)) {
				flightNotification
						.setOldPrice(subcribedFlight.getPriceBefore());
				flightNotification.setNewPrice(subcribedFlight.getPrice());
				flightNotification.setFlightChangeType(2);
			}
			flightNotification.setCurrencyCode(subcribedFlight
					.getCurrencyCode());

			DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			Date date = new Date();
			flightNotification.setSaveTime(dateFormat.format(date));

			ObjectMapper objectMapper = new ObjectMapper();
			String json = "";

			if (BroadcasterFactory.getDefault().lookup(user.getId()) != null) {
				// user with id i is online
				flightNotification.setStatus(0);
				FlightNotification temp = flightNotificationService
						.create(flightNotification);
				List<FlightNotification> listFlightJson = new ArrayList<FlightNotification>();
				listFlightJson.add(temp);
				List<FlightNotificationDTO> flightNotificationDTOs = flightNotificationService
						.convertToListFlightNotificationDTO(listFlightJson);

				try {
					json = objectMapper.writeValueAsString(flightNotificationDTOs);
					json = "10" + json;

					if (!BroadcasterFactory.getDefault().lookup(user.getId())
							.broadcast(json).isDone()) {
						temp.setStatus(0);
						try {
							flightNotificationService.update(temp);
						} catch (FlightNotificationNotFoundException e) {
							LOGGER.error("database error", e);
						}
					}
					
					LOGGER.error("SOMETHING CHANGE: " + json);
				} catch (JsonGenerationException e) {
					LOGGER.error("json error", e);
				} catch (JsonMappingException e) {
					LOGGER.error("json error", e);
				} catch (IOException e) {
					LOGGER.error("io error", e);
				}

			} else {
				flightNotification.setStatus(0);
				flightNotificationService.create(flightNotification);
			}

		}
	}

	private boolean sendEmailToListUserForChanging(SubscribedFlight subcribedFlight, List<User> users) {
		for(User user:users){
			if(!user.isSendViaEmail()){
				continue;
			}

			String emailStr;
			List<UserEmail> userEmail = userEmailService.findAllEmailByUserId(user.getId());
			Iterator<UserEmail> UEmail = userEmail.iterator();
			while (UEmail.hasNext()) {
				emailStr = UEmail.next().getEmail();
				if (subcribedFlight.getTypeOfTrip().equals("roundTrip")) {
					Email email = new Email();
					email = emailService.setRoundTripEmailTemplate(emailStr,
							subcribedFlight,
							"Alert Email - Flight Information Changed",
							"alert-template-roundtrip", user.getCurrency());
					try {
						emailService.sendEmail_(email);
					} catch (MessagingException e) {
						LOGGER.error("email can't be sent", e);
					}

				} else if (subcribedFlight.getTypeOfTrip().equals("oneWay")) {
					Email email = new Email();
					email = emailService.setOneWayEmailTemplate(emailStr,
							subcribedFlight,
							"Alert Email - Flight Information Changed",
							"alert-template-oneway", user.getCurrency());
					try {
						emailService.sendEmail_(email);
					} catch (MessagingException e) {
						LOGGER.error("email can't be sent", e);
					}
				}
			}
		}
		return true;
	}

	private boolean sendEmailToListUsersForCancel( SubscribedFlight subcribedFlight, List<User> users) {
		for(User user:users){
			if(!user.isSendViaEmail()){
				continue;
			}

			String emailStr;
			List<UserEmail> userEmail = userEmailService
					.findAllEmailByUserId(user.getId());
			Iterator<UserEmail> UEmail = userEmail.iterator();
			while (UEmail.hasNext()) {
				emailStr = UEmail.next().getEmail();
				if (subcribedFlight.getTypeOfTrip().equals("roundTrip")) {
					Email email = new Email();
					email = emailService.setRoundTripEmailCancelTemplate(
							emailStr, subcribedFlight,
							"Alert Email - Flight Canceled",
							"alert-cancel-template-roundtrip", user.getCurrency());
					try {
						emailService.sendEmail_(email);
					} catch (MessagingException e) {
						LOGGER.error("email can't be sent", e);
					}

				} else if (subcribedFlight.getTypeOfTrip().equals("oneWay")) {
					Email email = new Email();
					email = emailService.setOneWayEmailCancelTemplate(emailStr,
							subcribedFlight, "Alert Email - Flight Canceled",
							"alert-cancel-template-oneway", user.getCurrency());
					try {
						emailService.sendEmail_(email);
					} catch (MessagingException e) {
						LOGGER.error("email can't be sent", e);
					}
				}
			}// end while
		}
		return true;
	}

	/**
	 * Get list of parameters from subscribed flights.
	 *
	 * @return list of parameters
	 */
	public List<Parameters> getListOfParameters() {
		List<SubscribedFlight> subs = subcribedFlightService
				.findAvailableFlights();
		LOGGER.info("size of list subscribed flights: " + subs.size());
		return getListParameters(subs);
	}

	private List<Parameters> getListParameters(List<SubscribedFlight> subs) {
		// list of parameters
		List<Parameters> pars = new ArrayList<Parameters>();
		// list of flights of one parameter
		List<SubscribedFlight> ls;
		for (SubscribedFlight sub : subs) {
			Parameters par = getParameterObject(sub);
			Parameters temp = isContains(pars, par);
			if (temp != null) {
				maps.get(temp).add(sub);
				continue;
			}
			pars.add(par);
			ls = new ArrayList<SubscribedFlight>();
			ls.add(sub);
			maps.put(par, ls);
		}
		return pars;
	}

	private Parameters isContains(List<Parameters> pars, Parameters par) {
		for (Iterator<Parameters> iterator = pars.iterator(); iterator
				.hasNext();) {
			Parameters parameters = (Parameters) iterator.next();
			if (parameters.equals(par)) {
				return parameters;
			}
		}
		return null;
	}

	private Parameters getParameterObject(SubscribedFlight flight) {
		Parameters para = new Parameters();
		para.setFromLocation(flight.getOutboundCode());
		para.setToLocation(flight.getInboundCode());
		para.setNumAdult(Utils.convertToString(flight.getNumberAdult()));
		para.setNumChild(Utils.convertToString(flight.getNumberChild()));
		para.setNumInfant(Utils.convertToString(flight.getNumberInfant()));
		para.setTypeOfTrip(flight.getTypeOfTrip());
		para.setOutBound(flight.getOutBoundDate());
		para.setCabinClass(flight.getClassType());
		para.setInBound(flight.getInBoundDate());
		para.setNumStop(flight.getNumberStops());
		return para;
	}

	private List<SubscribedFlight> getChangePriceAndScheduleFlights(
			List<SubscribedFlight> changePriceFlights,
			List<SubscribedFlight> changeScheduleFlights) {
		List<SubscribedFlight> flights = new ArrayList<SubscribedFlight>();
		flights.addAll(changePriceFlights);
		for (SubscribedFlight subscribedFlight : changeScheduleFlights) {
			if (flights.contains(subscribedFlight))
				continue;
			flights.add(subscribedFlight);
		}
		return flights;
	}

	public List<SubscribedFlight> getListOfSubcribedFlight(Parameters par) {
		return maps.get(par);
	}

	public boolean setLock(boolean flag)
	{
		return paramLockerService.setLockFlag(flag);
	}

	public boolean getLock() {
		return paramLockerService.getLockFlag();
	}


}
